/**
 * @(#)CodeEntityMetricsAnalyzer.java, 2008-5-7.
 * Copyright 2008  Program Analysis Research Group(PARG),Institute of Software, Peking University.
 * All rights reserved.
 */
package edu.pku.sei.pgie.analyzer.internal;

import java.util.HashSet;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.AssertStatement;
import org.eclipse.jdt.core.dom.BreakStatement;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ContinueStatement;
import org.eclipse.jdt.core.dom.DoStatement;
import org.eclipse.jdt.core.dom.EmptyStatement;
import org.eclipse.jdt.core.dom.EnhancedForStatement;
import org.eclipse.jdt.core.dom.ExpressionStatement;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.ForStatement;
import org.eclipse.jdt.core.dom.IfStatement;
import org.eclipse.jdt.core.dom.LabeledStatement;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.ReturnStatement;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.SwitchStatement;
import org.eclipse.jdt.core.dom.SynchronizedStatement;
import org.eclipse.jdt.core.dom.ThisExpression;
import org.eclipse.jdt.core.dom.ThrowStatement;
import org.eclipse.jdt.core.dom.TryStatement;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.eclipse.jdt.core.dom.WhileStatement;

import edu.pku.sei.pgie.analyzer.core.Context;
import edu.pku.sei.pgie.analyzer.core.ICodeEntityConstants;
import edu.pku.sei.pgie.analyzer.core.ICollector;
import edu.pku.sei.pgie.analyzer.core.SourceTreeVisitor;
import edu.pku.sei.pgie.analyzer.util.TypeAnalyzer;

/**
 * @author HeLi
 */
public class CodeEntityMetricsAnalyzer extends SourceTreeVisitor implements
        ICodeEntityConstants {
    private static Log logger = LogFactory
            .getLog(CodeEntityMetricsAnalyzer.class);

    Context context;

    CodeEntitySymbolTable symbolTable;

    ASTParser parser;

    TypeAnalyzer typeAnalyzer;

    ICollector collector;

    MetricsVisitor metricsVisitor;

    public CodeEntityMetricsAnalyzer(Context context,
            CodeEntitySymbolTable symbolTable) throws Exception {
        this.context = context;
        this.symbolTable = symbolTable;
        parser = ASTParser.newParser(AST.JLS3);
        parser.setProject(context.getJavaProject());
        typeAnalyzer = new TypeAnalyzer();
        collector = context.getCollector();
        metricsVisitor = new MetricsVisitor();
    }

    private class MetricsVisitor extends VisitorWithContext {
        Metrics metrics;

        int methodCount;

        int typeCount;

        public MetricsVisitor() {
            super(true);
            metrics = new Metrics();
        }

        public Metrics getMetrics() {
            return metrics;
        }

        public boolean visit(MethodDeclaration node) {
            super.visit(node);
            VariableCounter vc = new VariableCounter();
            node.accept(vc);
            metrics.modify(Metrics.MetricsEnum.ANVPC, vc.getCounter());
            if(vc.getCounter() > 0) {
                logger.debug("variable size for method="+this.peekMethodFQN()+" size="+vc.getCounter());
            }
            StatementCounter lc = new StatementCounter();
            node.accept(lc);
            metrics.modify(Metrics.MetricsEnum.ANSPM, lc.getCounter());

            HeadDependancyCounter hc = new HeadDependancyCounter();
            node.accept(hc);
            metrics.modify(Metrics.MetricsEnum.ANMHDPC, hc
                    .getHeadDependancyCount());

            BodyDependancyCounter bc = new BodyDependancyCounter();
            node.accept(bc);
            metrics.modify(Metrics.MetricsEnum.ANMBDPC, bc
                    .getBodyDependancyCount());

            methodCount++;
            return true;
        }

        public boolean visit(TypeDeclaration node) {
            typeCount++;
            return super.visit(node);
        }

        public boolean visit(FieldDeclaration node) {
            ReferenceAndCompositionCounter rcc = new ReferenceAndCompositionCounter();
            node.accept(rcc);
            metrics.modify(Metrics.MetricsEnum.ANVCIPC, rcc
                    .getCompositionCount());
            metrics
                    .modify(Metrics.MetricsEnum.ANVRIPC, rcc
                            .getReferenceCount());
            return super.visit(node);
        }

        public void onFinishVisiting() {
            double ANSPM = metrics.get(Metrics.MetricsEnum.ANSPM);
            metrics.set(Metrics.MetricsEnum.ANSPM, ANSPM / methodCount);

            double ANVPC = metrics.get(Metrics.MetricsEnum.ANVPC);
            metrics.set(Metrics.MetricsEnum.ANVPC, ANVPC / typeCount);

            double ANVCIPC = metrics.get(Metrics.MetricsEnum.ANVCIPC);
            metrics.set(Metrics.MetricsEnum.ANVCIPC, ANVCIPC / typeCount);

            double ANVRIPC = metrics.get(Metrics.MetricsEnum.ANVRIPC);
            metrics.set(Metrics.MetricsEnum.ANVRIPC, ANVRIPC / typeCount);

            double ANMHDPC = metrics.get(Metrics.MetricsEnum.ANMHDPC);
            metrics.set(Metrics.MetricsEnum.ANMHDPC, ANMHDPC / typeCount);

            double ANMBDPC = metrics.get(Metrics.MetricsEnum.ANMBDPC);
            metrics.set(Metrics.MetricsEnum.ANMBDPC, ANMBDPC / typeCount);

            metrics.set(Metrics.MetricsEnum.ANMPC, new Double(methodCount)
                    / typeCount);
        }
    }

    private class HeadDependancyCounter extends VisitorWithContext {
        public HeadDependancyCounter() {
            super(false);
        }

        public int headDependancyCount = 0;

        public boolean visit(MethodDeclaration node) {
            Type returnType = node.getReturnType2();
            List parameters = node.parameters();
            TypeAnalyzer.TypeInfo returnTypeInfo = typeAnalyzer
                    .analyze(returnType);
            HashSet<String> headDependancyTypes = new HashSet<String>();
            if (returnTypeInfo != null) {
                String[] referencedTypes = returnTypeInfo.getReferencedTypes();
                for (String referencedType: referencedTypes) {
                    if (symbolTable.contains(referencedType)
                            && !referencedType.equals(peekTypeFQN())) {
                        headDependancyTypes.add(referencedType);
                    }
                }
            }
            for (int i = 0; i < parameters.size(); i++) {
                SingleVariableDeclaration svd = (SingleVariableDeclaration) parameters
                        .get(i);
                Type parameterType = svd.getType();
                TypeAnalyzer.TypeInfo typeInfo = typeAnalyzer
                        .analyze(parameterType);
                String[] referencedTypes = typeInfo.getReferencedTypes();
                for (String referencedType: referencedTypes) {
                    if (symbolTable.contains(referencedType)
                            && !referencedType.equals(peekTypeFQN())) {
                        headDependancyTypes.add(referencedType);
                    }
                }
            }
            headDependancyCount = headDependancyTypes.size();
            return false;
        }

        public int getHeadDependancyCount() {
            return headDependancyCount;
        }
    }

    private class BodyDependancyCounter extends VisitorWithContext {
        public BodyDependancyCounter() {
            super(true);
        }

        private int bodyDependancyCount;

        public boolean visit(VariableDeclarationStatement node) {
            Type variableType = node.getType();
            TypeAnalyzer.TypeInfo typeInfo = typeAnalyzer.analyze(variableType);
            String[] referencedTypes = typeInfo.getReferencedTypes();
            for (String referencedType: referencedTypes) {
                if (symbolTable.contains(referencedType)
                        && !referencedType.equals(peekTypeFQN())) {
                    bodyDependancyCount++;
                }
            }
            return false;
        }

        public int getBodyDependancyCount() {
            return bodyDependancyCount;
        }
    }

    private class ReferenceAndCompositionCounter extends VisitorWithContext {
        public ReferenceAndCompositionCounter() {
            super(true);
        }

        private int referenceCount;

        private int compositionCount;

        public boolean visit(FieldDeclaration node) {
            TypeAnalyzer.TypeInfo typeInfo = typeAnalyzer.analyze(node
                    .getType());
            HashSet<String> typeNames = new HashSet<String>();
            typeNames.add(typeInfo.typeName);
            if (typeInfo.isContainer) {
                String[] componentTypes = typeInfo.getContainerElementTypes();
                for (String componentType: componentTypes) {
                    typeNames.add(componentType);
                }
            }
            for (String typeName: typeNames) {
                if (symbolTable.contains(typeName)
                        && !typeName.equals(peekTypeFQN())) {
                    compositionCount++;
                }
            }

            return false;
        }

        public int getReferenceCount() {
            return referenceCount;
        }

        public int getCompositionCount() {
            return compositionCount;
        }
    }

    private static class VariableCounter extends ASTVisitor {
        int counter = 0;

        public int getCounter() {
            return counter;
        }

        public boolean visit(SingleVariableDeclaration node) {
        	counter += 1;
        	return true;
        }

        public boolean visit(VariableDeclarationStatement node) {
            counter += node.fragments().size();
            return true;
        }
    }

    private static class StatementCounter extends ASTVisitor {
        int counter = 0;

        private void increase() {
            counter++;
        }

        public int getCounter() {
            return counter;
        }

        public boolean visit(AssertStatement node) {
            increase();
            return true;
        }

        public boolean visit(BreakStatement node) {
            increase();
            return true;
        }

        public boolean visit(ContinueStatement node) {
            increase();
            return true;
        }

        public boolean visit(DoStatement node) {
            increase();
            return true;
        }

        public boolean visit(EmptyStatement node) {
            increase();
            return true;
        }

        public boolean visit(EnhancedForStatement node) {
            increase();
            return true;
        }

        public boolean visit(ExpressionStatement node) {
            increase();
            return true;
        }

        public boolean visit(ForStatement node) {
            increase();
            return true;
        }

        public boolean visit(IfStatement node) {
            increase();
            return true;
        }

        public boolean visit(LabeledStatement node) {
            increase();
            return true;
        }

        public boolean visit(ReturnStatement node) {
            increase();
            return true;
        }

        public boolean visit(SwitchStatement node) {
            increase();
            return true;
        }

        public boolean visit(SynchronizedStatement node) {
            increase();
            return true;
        }

        public boolean visit(ThisExpression node) {
            increase();
            return true;
        }

        public boolean visit(ThrowStatement node) {
            increase();
            return true;
        }

        public boolean visit(TryStatement node) {
            increase();
            return true;
        }

        public boolean visit(VariableDeclarationStatement node) {
            increase();
            return true;
        }

        public boolean visit(WhileStatement node) {
            increase();
            return true;
        }
    }

    public boolean visit(ICompilationUnit compilationUnit) {
        if (compilationUnit == null)
            return false;
        parser.setSource(compilationUnit);
        parser.setResolveBindings(true);
        CompilationUnit root = (CompilationUnit) parser.createAST(null);
        root.accept(metricsVisitor);
        return false;
    }

    public void collect() {
        metricsVisitor.onFinishVisiting();
        if (collector != null) {
            collector.collect(metricsVisitor.metrics);
        }
    }
}
